/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						 combat_model_window.cpp

	$Header: /heroes4/combat_model_window.cpp $

	$NoKeywords: $

 ************************************************************************/
// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "combat_model_window.h"

#include "actor_sequence.h"
#include "bitmap_group.h"
#include "bitmap_group_cache.h"
#include "combat_actor.h"
#include "combat_actor_action.h"
#include "combat_actor_model.h"
#include "creature_traits.h"
#include "direction.h"
#include "enum_operations.h"
#include "game_time.h"
#include "idle_processor.h"
#include "paint_surface.h"
#include "random.h"
#include "town_type.h"

namespace
{
	class t_backdrop_cache_set
	{
	public:
		t_backdrop_cache_set( double scale );

		double get_scale() const;
		t_bitmap_group_ptr get( t_town_type alignment ) const;
	protected:
		double               m_scale;
		t_bitmap_group_cache m_caches[k_town_type_count];
	};

	inline double t_backdrop_cache_set::get_scale() const
	{
		return m_scale;
	}

	inline t_bitmap_group_ptr t_backdrop_cache_set::get( t_town_type alignment ) const
	{
		return m_caches[alignment].get();
	}
};

t_backdrop_cache_set::t_backdrop_cache_set( double scale )
{
	t_town_type town;
	std::string layer_name;

	m_scale = scale;
	for (town = t_town_type(0); town < k_town_type_count; enum_incr( town ))
	{
		layer_name = "control.creature_model.";
		layer_name += k_town_keyword[town];
		m_caches[town] = t_bitmap_group_cache( layer_name, scale );
	}
}

static t_bitmap_group_ptr get_backdrop( t_town_type town, double scale )
{
	static std::vector<t_backdrop_cache_set> caches;
	int i;

	for (i = 0; i < caches.size(); i++)
		if (scale == caches[i].get_scale())
			break;
	if (i == caches.size())
		caches.push_back( t_backdrop_cache_set( scale ));
	return caches[i].get( town );
}

// -----------------------------------------------------------------
// window which shows a combat creature
// -----------------------------------------------------------------
class t_combat_actor_display : public t_unsaved_combat_actor, public t_idle_processor
{
public:
	t_combat_actor_display( t_combat_actor_model_ptr model_ptr,
		                    t_screen_point point, t_window* parent );

	t_screen_point					get_screen_point() const;
	virtual void					on_idle();
protected:
	void invalidate();
	void set_action( t_combat_actor_action_id action );

	t_combat_actor_action_id m_last_action;
	int						 m_cycles;
	t_screen_point			 m_screen_point;
	t_window*				 m_window;
	t_actor_sequence_ptr	 m_action_ptr[ k_combat_actor_action_count ];
};

inline t_screen_point t_combat_actor_display::get_screen_point() const
{
	return m_screen_point;
}

// -----------------------------------------------------------------
// window which shows a combat creature
// -----------------------------------------------------------------
t_combat_actor_display::t_combat_actor_display( t_combat_actor_model_ptr model_ptr,
		                                        t_screen_point point, t_window* parent )
					  : t_unsaved_combat_actor( 0, model_ptr, k_combat_actor_action_wait,
												k_direction_southwest )
{
	m_screen_point = point;
	m_window = parent;
	set_current_direction( k_direction_southwest );
	if (has_action( k_combat_actor_action_base_frame ))
		m_action_ptr[k_combat_actor_action_base_frame]
			= get_cache(k_combat_actor_action_base_frame, get_current_direction()).get();
	set_action( k_combat_actor_action_wait );
}

// -----------------------------------------------------------------
// window which shows a combat creature
// -----------------------------------------------------------------
void t_combat_actor_display::on_idle()
{
	int						 frame	    = get_current_frame_num() + 1;
	int						 frame_count = get_frame_count();
	t_combat_actor_action_id current_action = get_current_action();

	if (frame < frame_count)
	{
		invalidate();
		set_current_frame_num( frame );
		invalidate();
		return;
	}
	if ( current_action != k_combat_actor_action_prewalk
		 && current_action != k_combat_actor_action_walk
		 && current_action != k_combat_actor_action_base_frame
		 && has_action( k_combat_actor_action_base_frame ))
	{
		invalidate();
		set_current_action( k_combat_actor_action_base_frame );
		invalidate();
		return;
	}

	m_cycles--;
	if (m_cycles > 0)
	{
		invalidate();
		if (get_current_action() == k_combat_actor_action_base_frame
			&& has_action( m_last_action ))
			set_current_action( m_last_action );
		set_current_frame_num( 0 );
		invalidate();
		return;
	}

	switch (current_action)
	{
		case k_combat_actor_action_prewalk:
			set_action( k_combat_actor_action_walk );
			return;
		case k_combat_actor_action_walk:
			set_action( k_combat_actor_action_postwalk );
			return;
	}

	t_combat_actor_action_id action;

	switch (random(1,10))
	{
		case 1:
			action = k_combat_actor_action_block;
			break;

		case 2:
			action = k_combat_actor_action_cast_spell;
			break;

		case 3:
			action = k_combat_actor_action_fidget;
			break;

		case 4:
			action = k_combat_actor_action_flinch;
			break;

		case 5:
			action = k_combat_actor_action_melee;
			break;

		case 6:
			action = k_combat_actor_action_ranged;
			break;

		case 7:
			action = k_combat_actor_action_wait;
			break;

		default:
			action = k_combat_actor_action_prewalk;
			break;
	}

	if (m_last_action != k_combat_actor_action_wait)
		action = k_combat_actor_action_wait;
	if (!has_action( action ))
		action = k_combat_actor_action_wait;
	set_action( action );
}

// -----------------------------------------------------------------
// window which shows a combat creature
// -----------------------------------------------------------------
void t_combat_actor_display::invalidate()
{
	t_screen_rect	invalid_rect;

	invalid_rect = get_shadow_rect( 0 ) + m_screen_point;
	m_window->invalidate( invalid_rect );
	invalid_rect = get_rect( 0 ) + m_screen_point;
	m_window->invalidate( invalid_rect );
}

// -----------------------------------------------------------------
// window which shows a combat creature
// -----------------------------------------------------------------
void t_combat_actor_display::set_action( t_combat_actor_action_id action )
{
	m_last_action = action;
	invalidate();
	if (!has_action( action ))
		set_current_action( k_combat_actor_action_base_frame );
	else
	{
		set_current_action( action );
		m_action_ptr[action] = get_cache(action, get_current_direction()).get();
	}
	invalidate();
	switch (action)
	{
		case k_combat_actor_action_wait:
			m_cycles = (2 * get_frames_per_second() + get_frame_count() - 1) / get_frame_count();
			break;

		case k_combat_actor_action_walk:
			m_cycles = (6 * get_frames_per_second() + get_frame_count() - 1) / get_frame_count();
			break;

		default:
			m_cycles = 1;
	}
	set_delay( 1000 / get_frames_per_second() );
}

// -----------------------------------------------------------------
// window which shows a combat creature
// -----------------------------------------------------------------
t_combat_model_window::t_combat_model_window( t_screen_rect const& rect, t_window* parent )
					 : t_window( rect, k_no_transparency, parent )
{
	initialize();
}

// -----------------------------------------------------------------
// window which shows a combat creature
// -----------------------------------------------------------------
t_combat_model_window::t_combat_model_window( t_screen_rect const& rect, t_window* parent,
		                                      t_creature_type creature )
					 : t_window( rect, k_no_transparency, parent )
{
	initialize();
	set( creature );
}

// -----------------------------------------------------------------
// window which shows a combat creature
// -----------------------------------------------------------------
t_combat_model_window::t_combat_model_window( t_screen_rect const& rect, t_window* parent, 
											  t_town_type alignment, bool is_male, 
											  bool has_archery, bool is_spellcaster )
					 : t_window( rect, k_no_transparency, parent )
{
	initialize();
	set( alignment, is_male, has_archery, is_spellcaster );
}

// -----------------------------------------------------------------
// window which shows a combat creature
// -----------------------------------------------------------------
static t_bitmap_group_cache const k_layout( "control.creature_model" );

void t_combat_model_window::initialize()
{
	t_screen_rect layout_rect;
	double        width_scale;

	m_layout = k_layout.get();
	layout_rect = m_layout->find( "1_of_1" )->get_rect();
	width_scale = double(get_width()) / layout_rect.width();
	m_scale = double( get_height()) / layout_rect.height();
	if (width_scale < m_scale)
		m_scale = width_scale;
	if (m_scale > 1.0)
		m_scale = 1.0;
}

// -----------------------------------------------------------------
// window which shows a combat creature
// -----------------------------------------------------------------
void t_combat_model_window::set( t_creature_type creature )
{
	m_background_bitmap = get_backdrop( get_traits( creature ).alignment, m_scale );
	set( get_combat_actor_model( creature, m_scale ), 3 );
}

// -----------------------------------------------------------------
// Set the backdrop only.
// -----------------------------------------------------------------
void t_combat_model_window::set( t_town_type town )
{
	m_background_bitmap = get_backdrop( town, m_scale );
}

// -----------------------------------------------------------------
// window which shows a combat creature
// -----------------------------------------------------------------
void t_combat_model_window::set( t_town_type alignment, bool is_male, bool has_archery,
								 bool is_spellcaster )
{
	m_background_bitmap = get_backdrop( alignment, m_scale );
	set( get_combat_actor_model( alignment, is_male, has_archery, is_spellcaster, m_scale ), 1 );
}

// -----------------------------------------------------------------
// window which shows a combat creature
// -----------------------------------------------------------------
void t_combat_model_window::set( t_combat_actor_model_ptr model_ptr, int maximum_figures )
{
	t_actor_sequence_cache cache;
	t_screen_rect          rect;
	t_screen_rect          layout_rect;
	t_actor_sequence_ptr   sequence;
	int                    count;
	int                    i;
	std::string            layer_name;
	t_screen_point		   point;
	int					   footprint_size = model_ptr->get_footprint_size();
	t_combat_actor_action_id action;

	m_actors.clear();
	if (model_ptr->has_action( k_combat_actor_action_walk, k_direction_southwest ))
		action = k_combat_actor_action_walk;
	else
		action = k_combat_actor_action_wait;
	cache = model_ptr->get_sequence_cache( action, k_direction_southwest );
	sequence = cache.get();
	rect = sequence->get_rect();
	footprint_size = k_battlefield_cell_height * footprint_size * m_scale;
	
	for (count = maximum_figures; count > 1; count--)
	{
		layer_name = format_string( "1_of_%i", count );
		layout_rect = m_layout->find( layer_name )->get_rect();
		if (layout_rect.width() * m_scale < rect.width())
			continue;
		if (layout_rect.height() * m_scale >= rect.height())
			break;
	}
	for (i = 1; i <= count; i++)
	{
		layer_name = format_string( "%i_of_%i", i, count );
		layout_rect = m_layout->find( layer_name )->get_rect();
		point.x = (layout_rect.left + layout_rect.right) * m_scale / 2;   
		point.y = (layout_rect.top + layout_rect.bottom) * m_scale / 2 
			      + rect.height() / 2 - footprint_size;
		m_actors.push_back( new t_combat_actor_display( model_ptr, point, this ) );
	}
	m_background_origin = (get_client_rect().size() - m_background_bitmap->get_rect().size()) / 2;
	invalidate();
}

// -----------------------------------------------------------------
// window which shows a combat creature
// -----------------------------------------------------------------
void t_combat_model_window::paint( t_paint_surface & paint_surface )
{
	t_screen_rect			paint_rect = paint_surface.get_rect();
	t_abstract_bitmap16_ptr paint_bitmap_ptr = paint_surface.get_bitmap();
	t_bitmap_layer const*   layer;
	int                     i;

	if (m_background_bitmap != 0)
	{
		layer = m_background_bitmap->front().get();
		layer->draw_to( paint_rect - m_background_origin, *paint_bitmap_ptr );
	}

	// Now paint all of the object shadows
	for (i = 0; i < m_actors.size(); i++)
	{
		m_actors[i]->draw_shadow_to( 0, *paint_bitmap_ptr, 
								     m_actors[i]->get_screen_point() - paint_rect.top_left() );
	}

	// Now paint all of the objects
	for (i = 0; i < m_actors.size(); i++)
	{
		m_actors[i]->draw_to( 0, *paint_bitmap_ptr, 
			                  m_actors[i]->get_screen_point() - paint_rect.top_left() );
	}
}

